summaryrefslogtreecommitdiffstats
path: root/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java878
1 files changed, 878 insertions, 0 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java
new file mode 100644
index 000000000..043a164ce
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.java
@@ -0,0 +1,878 @@
+/**
+ * Copyright 2013 Dolphin Emulator Project
+ * Licensed under GPLv2+
+ * Refer to the license.txt file included.
+ */
+
+package org.yuzu.yuzu_emu.overlay;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.preference.PreferenceManager;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import org.yuzu.yuzu_emu.NativeLibrary;
+import org.yuzu.yuzu_emu.NativeLibrary.ButtonState;
+import org.yuzu.yuzu_emu.NativeLibrary.ButtonType;
+import org.yuzu.yuzu_emu.R;
+import org.yuzu.yuzu_emu.utils.EmulationMenuSettings;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Draws the interactive input overlay on top of the
+ * {@link SurfaceView} that is rendering emulation.
+ */
+public final class InputOverlay extends SurfaceView implements OnTouchListener {
+ private final Set<InputOverlayDrawableButton> overlayButtons = new HashSet<>();
+ private final Set<InputOverlayDrawableDpad> overlayDpads = new HashSet<>();
+ private final Set<InputOverlayDrawableJoystick> overlayJoysticks = new HashSet<>();
+
+ private boolean mIsInEditMode = false;
+ private InputOverlayDrawableButton mButtonBeingConfigured;
+ private InputOverlayDrawableDpad mDpadBeingConfigured;
+ private InputOverlayDrawableJoystick mJoystickBeingConfigured;
+
+ private SharedPreferences mPreferences;
+
+ // Stores the ID of the pointer that interacted with the 3DS touchscreen.
+ private int mTouchscreenPointerId = -1;
+
+ /**
+ * Constructor
+ *
+ * @param context The current {@link Context}.
+ * @param attrs {@link AttributeSet} for parsing XML attributes.
+ */
+ public InputOverlay(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
+ if (!mPreferences.getBoolean("OverlayInit", false)) {
+ defaultOverlay();
+ }
+
+ // Reset 3ds touchscreen pointer ID
+ mTouchscreenPointerId = -1;
+
+ // Load the controls.
+ refreshControls();
+
+ // Set the on touch listener.
+ setOnTouchListener(this);
+
+ // Force draw
+ setWillNotDraw(false);
+
+ // Request focus for the overlay so it has priority on presses.
+ requestFocus();
+ }
+
+ /**
+ * Resizes a {@link Bitmap} by a given scale factor
+ *
+ * @param context The current {@link Context}
+ * @param bitmap The {@link Bitmap} to scale.
+ * @param scale The scale factor for the bitmap.
+ * @return The scaled {@link Bitmap}
+ */
+ public static Bitmap resizeBitmap(Context context, Bitmap bitmap, float scale) {
+ // Determine the button size based on the smaller screen dimension.
+ // This makes sure the buttons are the same size in both portrait and landscape.
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ int minDimension = Math.min(dm.widthPixels, dm.heightPixels);
+
+ return Bitmap.createScaledBitmap(bitmap,
+ (int) (minDimension * scale),
+ (int) (minDimension * scale),
+ true);
+ }
+
+ /**
+ * Initializes an InputOverlayDrawableButton, given by resId, with all of the
+ * parameters set for it to be properly shown on the InputOverlay.
+ * <p>
+ * This works due to the way the X and Y coordinates are stored within
+ * the {@link SharedPreferences}.
+ * <p>
+ * In the input overlay configuration menu,
+ * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
+ * the X and Y coordinates of the button at the END of its touch event
+ * (when you remove your finger/stylus from the touchscreen) are then stored
+ * within a SharedPreferences instance so that those values can be retrieved here.
+ * <p>
+ * This has a few benefits over the conventional way of storing the values
+ * (ie. within the yuzu ini file).
+ * <ul>
+ * <li>No native calls</li>
+ * <li>Keeps Android-only values inside the Android environment</li>
+ * </ul>
+ * <p>
+ * Technically no modifications should need to be performed on the returned
+ * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
+ * for Android to call the onDraw method.
+ *
+ * @param context The current {@link Context}.
+ * @param defaultResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Default State).
+ * @param pressedResId The resource ID of the {@link Drawable} to get the {@link Bitmap} of (Pressed State).
+ * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
+ * @return An {@link InputOverlayDrawableButton} with the correct drawing bounds set.
+ */
+ private static InputOverlayDrawableButton initializeOverlayButton(Context context,
+ int defaultResId, int pressedResId, int buttonId, String orientation) {
+ // Resources handle for fetching the initial Drawable resource.
+ final Resources res = context.getResources();
+
+ // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Decide scale based on button ID and user preference
+ float scale;
+
+ switch (buttonId) {
+ case ButtonType.BUTTON_HOME:
+ case ButtonType.BUTTON_START:
+ case ButtonType.BUTTON_SELECT:
+ scale = 0.08f;
+ break;
+ case ButtonType.TRIGGER_L:
+ case ButtonType.TRIGGER_R:
+ case ButtonType.BUTTON_ZL:
+ case ButtonType.BUTTON_ZR:
+ scale = 0.18f;
+ break;
+ default:
+ scale = 0.11f;
+ break;
+ }
+
+ scale *= (sPrefs.getInt("controlScale", 50) + 50);
+ scale /= 100;
+
+ // Initialize the InputOverlayDrawableButton.
+ final Bitmap defaultStateBitmap =
+ resizeBitmap(context, BitmapFactory.decodeResource(res, defaultResId), scale);
+ final Bitmap pressedStateBitmap =
+ resizeBitmap(context, BitmapFactory.decodeResource(res, pressedResId), scale);
+ final InputOverlayDrawableButton overlayDrawable =
+ new InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId);
+
+ // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
+ // These were set in the input overlay configuration menu.
+ String xKey;
+ String yKey;
+
+ xKey = buttonId + orientation + "-X";
+ yKey = buttonId + orientation + "-Y";
+
+ int drawableX = (int) sPrefs.getFloat(xKey, 0f);
+ int drawableY = (int) sPrefs.getFloat(yKey, 0f);
+
+ int width = overlayDrawable.getWidth();
+ int height = overlayDrawable.getHeight();
+
+ // Now set the bounds for the InputOverlayDrawableButton.
+ // This will dictate where on the screen (and the what the size) the InputOverlayDrawableButton will be.
+ overlayDrawable.setBounds(drawableX, drawableY, drawableX + width, drawableY + height);
+
+ // Need to set the image's position
+ overlayDrawable.setPosition(drawableX, drawableY);
+
+ return overlayDrawable;
+ }
+
+ /**
+ * Initializes an {@link InputOverlayDrawableDpad}
+ *
+ * @param context The current {@link Context}.
+ * @param defaultResId The {@link Bitmap} resource ID of the default sate.
+ * @param pressedOneDirectionResId The {@link Bitmap} resource ID of the pressed sate in one direction.
+ * @param pressedTwoDirectionsResId The {@link Bitmap} resource ID of the pressed sate in two directions.
+ * @param buttonUp Identifier for the up button.
+ * @param buttonDown Identifier for the down button.
+ * @param buttonLeft Identifier for the left button.
+ * @param buttonRight Identifier for the right button.
+ * @return the initialized {@link InputOverlayDrawableDpad}
+ */
+ private static InputOverlayDrawableDpad initializeOverlayDpad(Context context,
+ int defaultResId,
+ int pressedOneDirectionResId,
+ int pressedTwoDirectionsResId,
+ int buttonUp,
+ int buttonDown,
+ int buttonLeft,
+ int buttonRight,
+ String orientation) {
+ // Resources handle for fetching the initial Drawable resource.
+ final Resources res = context.getResources();
+
+ // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Decide scale based on button ID and user preference
+ float scale = 0.22f;
+
+ scale *= (sPrefs.getInt("controlScale", 50) + 50);
+ scale /= 100;
+
+ // Initialize the InputOverlayDrawableDpad.
+ final Bitmap defaultStateBitmap =
+ resizeBitmap(context, BitmapFactory.decodeResource(res, defaultResId), scale);
+ final Bitmap pressedOneDirectionStateBitmap =
+ resizeBitmap(context, BitmapFactory.decodeResource(res, pressedOneDirectionResId),
+ scale);
+ final Bitmap pressedTwoDirectionsStateBitmap =
+ resizeBitmap(context, BitmapFactory.decodeResource(res, pressedTwoDirectionsResId),
+ scale);
+ final InputOverlayDrawableDpad overlayDrawable =
+ new InputOverlayDrawableDpad(res, defaultStateBitmap,
+ pressedOneDirectionStateBitmap, pressedTwoDirectionsStateBitmap,
+ buttonUp, buttonDown, buttonLeft, buttonRight);
+
+ // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
+ // These were set in the input overlay configuration menu.
+ int drawableX = (int) sPrefs.getFloat(buttonUp + orientation + "-X", 0f);
+ int drawableY = (int) sPrefs.getFloat(buttonUp + orientation + "-Y", 0f);
+
+ int width = overlayDrawable.getWidth();
+ int height = overlayDrawable.getHeight();
+
+ // Now set the bounds for the InputOverlayDrawableDpad.
+ // This will dictate where on the screen (and the what the size) the InputOverlayDrawableDpad will be.
+ overlayDrawable.setBounds(drawableX, drawableY, drawableX + width, drawableY + height);
+
+ // Need to set the image's position
+ overlayDrawable.setPosition(drawableX, drawableY);
+
+ return overlayDrawable;
+ }
+
+ /**
+ * Initializes an {@link InputOverlayDrawableJoystick}
+ *
+ * @param context The current {@link Context}
+ * @param resOuter Resource ID for the outer image of the joystick (the static image that shows the circular bounds).
+ * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
+ * @param pressedResInner Resource ID for the pressed inner image of the joystick.
+ * @param joystick Identifier for which joystick this is.
+ * @return the initialized {@link InputOverlayDrawableJoystick}.
+ */
+ private static InputOverlayDrawableJoystick initializeOverlayJoystick(Context context,
+ int resOuter, int defaultResInner, int pressedResInner, int joystick, String orientation) {
+ // Resources handle for fetching the initial Drawable resource.
+ final Resources res = context.getResources();
+
+ // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ // Decide scale based on user preference
+ float scale = 0.275f;
+ scale *= (sPrefs.getInt("controlScale", 50) + 50);
+ scale /= 100;
+
+ // Initialize the InputOverlayDrawableJoystick.
+ final Bitmap bitmapOuter =
+ resizeBitmap(context, BitmapFactory.decodeResource(res, resOuter), scale);
+ final Bitmap bitmapInnerDefault = BitmapFactory.decodeResource(res, defaultResInner);
+ final Bitmap bitmapInnerPressed = BitmapFactory.decodeResource(res, pressedResInner);
+
+ // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
+ // These were set in the input overlay configuration menu.
+ int drawableX = (int) sPrefs.getFloat(joystick + orientation + "-X", 0f);
+ int drawableY = (int) sPrefs.getFloat(joystick + orientation + "-Y", 0f);
+
+ // Decide inner scale based on joystick ID
+ float outerScale = 1.f;
+ if (joystick == ButtonType.STICK_C) {
+ outerScale = 2.f;
+ }
+
+ // Now set the bounds for the InputOverlayDrawableJoystick.
+ // This will dictate where on the screen (and the what the size) the InputOverlayDrawableJoystick will be.
+ int outerSize = bitmapOuter.getWidth();
+ Rect outerRect = new Rect(drawableX, drawableY, drawableX + (int) (outerSize / outerScale), drawableY + (int) (outerSize / outerScale));
+ Rect innerRect = new Rect(0, 0, (int) (outerSize / outerScale), (int) (outerSize / outerScale));
+
+ // Send the drawableId to the joystick so it can be referenced when saving control position.
+ final InputOverlayDrawableJoystick overlayDrawable
+ = new InputOverlayDrawableJoystick(res, bitmapOuter,
+ bitmapInnerDefault, bitmapInnerPressed,
+ outerRect, innerRect, joystick);
+
+ // Need to set the image's position
+ overlayDrawable.setPosition(drawableX, drawableY);
+
+ return overlayDrawable;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ for (InputOverlayDrawableButton button : overlayButtons) {
+ button.draw(canvas);
+ }
+
+ for (InputOverlayDrawableDpad dpad : overlayDpads) {
+ dpad.draw(canvas);
+ }
+
+ for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
+ joystick.draw(canvas);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (isInEditMode()) {
+ return onTouchWhileEditing(event);
+ }
+
+ int pointerIndex = event.getActionIndex();
+
+ if (mPreferences.getBoolean("isTouchEnabled", true)) {
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (NativeLibrary.onTouchEvent(event.getX(pointerIndex), event.getY(pointerIndex), true)) {
+ mTouchscreenPointerId = event.getPointerId(pointerIndex);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mTouchscreenPointerId == event.getPointerId(pointerIndex)) {
+ // We don't really care where the touch has been released. We only care whether it has been
+ // released or not.
+ NativeLibrary.onTouchEvent(0, 0, false);
+ mTouchscreenPointerId = -1;
+ }
+ break;
+ }
+
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ if (mTouchscreenPointerId == event.getPointerId(i)) {
+ NativeLibrary.onTouchMoved(event.getX(i), event.getY(i));
+ }
+ }
+ }
+
+ for (InputOverlayDrawableButton button : overlayButtons) {
+ // Determine the button state to apply based on the MotionEvent action flag.
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // If a pointer enters the bounds of a button, press that button.
+ if (button.getBounds()
+ .contains((int) event.getX(pointerIndex), (int) event.getY(pointerIndex))) {
+ button.setPressedState(true);
+ button.setTrackId(event.getPointerId(pointerIndex));
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.getId(),
+ ButtonState.PRESSED);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ // If a pointer ends, release the button it was pressing.
+ if (button.getTrackId() == event.getPointerId(pointerIndex)) {
+ button.setPressedState(false);
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.getId(),
+ ButtonState.RELEASED);
+ }
+ break;
+ }
+ }
+
+ for (InputOverlayDrawableDpad dpad : overlayDpads) {
+ // Determine the button state to apply based on the MotionEvent action flag.
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // If a pointer enters the bounds of a button, press that button.
+ if (dpad.getBounds()
+ .contains((int) event.getX(pointerIndex), (int) event.getY(pointerIndex))) {
+ dpad.setTrackId(event.getPointerId(pointerIndex));
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ // If a pointer ends, release the buttons.
+ if (dpad.getTrackId() == event.getPointerId(pointerIndex)) {
+ for (int i = 0; i < 4; i++) {
+ dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT);
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(i),
+ NativeLibrary.ButtonState.RELEASED);
+ }
+ dpad.setTrackId(-1);
+ }
+ break;
+ }
+
+ if (dpad.getTrackId() != -1) {
+ for (int i = 0; i < event.getPointerCount(); i++) {
+ if (dpad.getTrackId() == event.getPointerId(i)) {
+ float touchX = event.getX(i);
+ float touchY = event.getY(i);
+ float maxY = dpad.getBounds().bottom;
+ float maxX = dpad.getBounds().right;
+ touchX -= dpad.getBounds().centerX();
+ maxX -= dpad.getBounds().centerX();
+ touchY -= dpad.getBounds().centerY();
+ maxY -= dpad.getBounds().centerY();
+ final float AxisX = touchX / maxX;
+ final float AxisY = touchY / maxY;
+
+ boolean up = false;
+ boolean down = false;
+ boolean left = false;
+ boolean right = false;
+ if (EmulationMenuSettings.getDpadSlideEnable() ||
+ (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN ||
+ (event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
+ if (AxisY < -InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(0),
+ NativeLibrary.ButtonState.PRESSED);
+ up = true;
+ } else {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(0),
+ NativeLibrary.ButtonState.RELEASED);
+ }
+ if (AxisY > InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(1),
+ NativeLibrary.ButtonState.PRESSED);
+ down = true;
+ } else {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(1),
+ NativeLibrary.ButtonState.RELEASED);
+ }
+ if (AxisX < -InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(2),
+ NativeLibrary.ButtonState.PRESSED);
+ left = true;
+ } else {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(2),
+ NativeLibrary.ButtonState.RELEASED);
+ }
+ if (AxisX > InputOverlayDrawableDpad.VIRT_AXIS_DEADZONE) {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(3),
+ NativeLibrary.ButtonState.PRESSED);
+ right = true;
+ } else {
+ NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.getId(3),
+ NativeLibrary.ButtonState.RELEASED);
+ }
+
+ // Set state
+ if (up) {
+ if (left)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_LEFT);
+ else if (right)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_RIGHT);
+ else
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP);
+ } else if (down) {
+ if (left)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_LEFT);
+ else if (right)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_RIGHT);
+ else
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN);
+ } else if (left) {
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_LEFT);
+ } else if (right) {
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_RIGHT);
+ } else {
+ dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
+ joystick.TrackEvent(event);
+ int axisID = joystick.getId();
+ float[] axises = joystick.getAxisValues();
+
+ NativeLibrary
+ .onGamePadMoveEvent(NativeLibrary.TouchScreenDevice, axisID, axises[0], axises[1]);
+ }
+
+ invalidate();
+
+ return true;
+ }
+
+ public boolean onTouchWhileEditing(MotionEvent event) {
+ int pointerIndex = event.getActionIndex();
+ int fingerPositionX = (int) event.getX(pointerIndex);
+ int fingerPositionY = (int) event.getY(pointerIndex);
+
+ String orientation =
+ getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
+ "-Portrait" : "";
+
+ // Maybe combine Button and Joystick as subclasses of the same parent?
+ // Or maybe create an interface like IMoveableHUDControl?
+
+ for (InputOverlayDrawableButton button : overlayButtons) {
+ // Determine the button state to apply based on the MotionEvent action flag.
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // If no button is being moved now, remember the currently touched button to move.
+ if (mButtonBeingConfigured == null &&
+ button.getBounds().contains(fingerPositionX, fingerPositionY)) {
+ mButtonBeingConfigured = button;
+ mButtonBeingConfigured.onConfigureTouch(event);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mButtonBeingConfigured != null) {
+ mButtonBeingConfigured.onConfigureTouch(event);
+ invalidate();
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mButtonBeingConfigured == button) {
+ // Persist button position by saving new place.
+ saveControlPosition(mButtonBeingConfigured.getId(),
+ mButtonBeingConfigured.getBounds().left,
+ mButtonBeingConfigured.getBounds().top, orientation);
+ mButtonBeingConfigured = null;
+ }
+ break;
+ }
+ }
+
+ for (InputOverlayDrawableDpad dpad : overlayDpads) {
+ // Determine the button state to apply based on the MotionEvent action flag.
+ switch (event.getAction() & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // If no button is being moved now, remember the currently touched button to move.
+ if (mButtonBeingConfigured == null &&
+ dpad.getBounds().contains(fingerPositionX, fingerPositionY)) {
+ mDpadBeingConfigured = dpad;
+ mDpadBeingConfigured.onConfigureTouch(event);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mDpadBeingConfigured != null) {
+ mDpadBeingConfigured.onConfigureTouch(event);
+ invalidate();
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mDpadBeingConfigured == dpad) {
+ // Persist button position by saving new place.
+ saveControlPosition(mDpadBeingConfigured.getId(0),
+ mDpadBeingConfigured.getBounds().left, mDpadBeingConfigured.getBounds().top,
+ orientation);
+ mDpadBeingConfigured = null;
+ }
+ break;
+ }
+ }
+
+ for (InputOverlayDrawableJoystick joystick : overlayJoysticks) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mJoystickBeingConfigured == null &&
+ joystick.getBounds().contains(fingerPositionX, fingerPositionY)) {
+ mJoystickBeingConfigured = joystick;
+ mJoystickBeingConfigured.onConfigureTouch(event);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mJoystickBeingConfigured != null) {
+ mJoystickBeingConfigured.onConfigureTouch(event);
+ invalidate();
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mJoystickBeingConfigured != null) {
+ saveControlPosition(mJoystickBeingConfigured.getId(),
+ mJoystickBeingConfigured.getBounds().left,
+ mJoystickBeingConfigured.getBounds().top, orientation);
+ mJoystickBeingConfigured = null;
+ }
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ private void setDpadState(InputOverlayDrawableDpad dpad, boolean up, boolean down, boolean left,
+ boolean right) {
+ if (up) {
+ if (left)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_LEFT);
+ else if (right)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_RIGHT);
+ else
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP);
+ } else if (down) {
+ if (left)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_LEFT);
+ else if (right)
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_RIGHT);
+ else
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN);
+ } else if (left) {
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_LEFT);
+ } else if (right) {
+ dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_RIGHT);
+ }
+ }
+
+ private void addOverlayControls(String orientation) {
+ if (mPreferences.getBoolean("buttonToggle0", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_a,
+ R.drawable.button_a_pressed, ButtonType.BUTTON_A, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle1", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_b,
+ R.drawable.button_b_pressed, ButtonType.BUTTON_B, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle2", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_x,
+ R.drawable.button_x_pressed, ButtonType.BUTTON_X, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle3", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_y,
+ R.drawable.button_y_pressed, ButtonType.BUTTON_Y, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle4", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_l,
+ R.drawable.button_l_pressed, ButtonType.TRIGGER_L, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle5", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_r,
+ R.drawable.button_r_pressed, ButtonType.TRIGGER_R, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle6", false)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_zl,
+ R.drawable.button_zl_pressed, ButtonType.BUTTON_ZL, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle7", false)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_zr,
+ R.drawable.button_zr_pressed, ButtonType.BUTTON_ZR, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle8", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_start,
+ R.drawable.button_start_pressed, ButtonType.BUTTON_START, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle9", true)) {
+ overlayButtons.add(initializeOverlayButton(getContext(), R.drawable.button_select,
+ R.drawable.button_select_pressed, ButtonType.BUTTON_SELECT, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle10", true)) {
+ overlayDpads.add(initializeOverlayDpad(getContext(), R.drawable.dpad,
+ R.drawable.dpad_pressed_one_direction,
+ R.drawable.dpad_pressed_two_directions,
+ ButtonType.DPAD_UP, ButtonType.DPAD_DOWN,
+ ButtonType.DPAD_LEFT, ButtonType.DPAD_RIGHT, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle11", true)) {
+ overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.stick_main_range,
+ R.drawable.stick_main, R.drawable.stick_main_pressed,
+ ButtonType.STICK_LEFT, orientation));
+ }
+ if (mPreferences.getBoolean("buttonToggle12", false)) {
+ overlayJoysticks.add(initializeOverlayJoystick(getContext(), R.drawable.stick_c_range,
+ R.drawable.stick_c, R.drawable.stick_c_pressed, ButtonType.STICK_C, orientation));
+ }
+ }
+
+ public void refreshControls() {
+ // Remove all the overlay buttons from the HashSet.
+ overlayButtons.clear();
+ overlayDpads.clear();
+ overlayJoysticks.clear();
+
+ String orientation =
+ getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ?
+ "-Portrait" : "";
+
+ // Add all the enabled overlay items back to the HashSet.
+ if (EmulationMenuSettings.getShowOverlay()) {
+ addOverlayControls(orientation);
+ }
+
+ invalidate();
+ }
+
+ private void saveControlPosition(int sharedPrefsId, int x, int y, String orientation) {
+ final SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ SharedPreferences.Editor sPrefsEditor = sPrefs.edit();
+ sPrefsEditor.putFloat(sharedPrefsId + orientation + "-X", x);
+ sPrefsEditor.putFloat(sharedPrefsId + orientation + "-Y", y);
+ sPrefsEditor.apply();
+ }
+
+ public void setIsInEditMode(boolean isInEditMode) {
+ mIsInEditMode = isInEditMode;
+ }
+
+ private void defaultOverlay() {
+ if (!mPreferences.getBoolean("OverlayInit", false)) {
+ // It's possible that a user has created their overlay before this was added
+ // Only change the overlay if the 'A' button is not in the upper corner.
+ if (mPreferences.getFloat(ButtonType.BUTTON_A + "-X", 0f) == 0f) {
+ defaultOverlayLandscape();
+ }
+ if (mPreferences.getFloat(ButtonType.BUTTON_A + "-Portrait" + "-X", 0f) == 0f) {
+ defaultOverlayPortrait();
+ }
+ }
+
+ SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
+ sPrefsEditor.putBoolean("OverlayInit", true);
+ sPrefsEditor.apply();
+ }
+
+ public void resetButtonPlacement() {
+ boolean isLandscape =
+ getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
+
+ if (isLandscape) {
+ defaultOverlayLandscape();
+ } else {
+ defaultOverlayPortrait();
+ }
+
+ refreshControls();
+ }
+
+ private void defaultOverlayLandscape() {
+ SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
+ // Get screen size
+ Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ display.getMetrics(outMetrics);
+ float maxX = outMetrics.heightPixels;
+ float maxY = outMetrics.widthPixels;
+ // Height and width changes depending on orientation. Use the larger value for height.
+ if (maxY > maxX) {
+ float tmp = maxX;
+ maxX = maxY;
+ maxY = tmp;
+ }
+ Resources res = getResources();
+
+ // Each value is a percent from max X/Y stored as an int. Have to bring that value down
+ // to a decimal before multiplying by MAX X/Y.
+ sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_A + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_B + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_X + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_Y + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.DPAD_UP + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_L + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_R + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_START + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_START + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.STICK_C + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_C_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.STICK_C + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_C_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.STICK_LEFT + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.STICK_LEFT + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_Y) / 1000) * maxY));
+
+ // We want to commit right away, otherwise the overlay could load before this is saved.
+ sPrefsEditor.commit();
+ }
+
+ private void defaultOverlayPortrait() {
+ SharedPreferences.Editor sPrefsEditor = mPreferences.edit();
+ // Get screen size
+ Display display = ((Activity) getContext()).getWindowManager().getDefaultDisplay();
+ DisplayMetrics outMetrics = new DisplayMetrics();
+ display.getMetrics(outMetrics);
+ float maxX = outMetrics.heightPixels;
+ float maxY = outMetrics.widthPixels;
+ // Height and width changes depending on orientation. Use the larger value for height.
+ if (maxY < maxX) {
+ float tmp = maxX;
+ maxX = maxY;
+ maxY = tmp;
+ }
+ Resources res = getResources();
+ String portrait = "-Portrait";
+
+ // Each value is a percent from max X/Y stored as an int. Have to bring that value down
+ // to a decimal before multiplying by MAX X/Y.
+ sPrefsEditor.putFloat(ButtonType.BUTTON_A + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_A + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_A_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_B + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_B + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_B_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_X + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_X + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_X_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_Y + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_Y + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_Y_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZL + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZL_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_ZR + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_ZR_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.DPAD_UP + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.DPAD_UP + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_UP_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_L + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_L + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_L_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_R + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.TRIGGER_R + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_TRIGGER_R_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_START + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_START + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_START_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_SELECT + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_SELECT_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.BUTTON_HOME + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_BUTTON_HOME_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.STICK_C + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_C_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.STICK_C + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_C_PORTRAIT_Y) / 1000) * maxY));
+ sPrefsEditor.putFloat(ButtonType.STICK_LEFT + portrait + "-X", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_PORTRAIT_X) / 1000) * maxX));
+ sPrefsEditor.putFloat(ButtonType.STICK_LEFT + portrait + "-Y", (((float) res.getInteger(R.integer.N3DS_STICK_MAIN_PORTRAIT_Y) / 1000) * maxY));
+
+ // We want to commit right away, otherwise the overlay could load before this is saved.
+ sPrefsEditor.commit();
+ }
+
+ public boolean isInEditMode() {
+ return mIsInEditMode;
+ }
+}